#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "raylib.h"
#include "pub_lib.h"

const int screenWidth = 1024;
const int screenHeight = 768;

Image cat;
Texture2D gtexture;
char* hint_str = NULL;
run_state gstate = STATE_IDLE;
static my_image effect_data;

void print_to_terminal(void)
{
	//print useful information
	printf("图像特效显示，请输入数字 1 - 9：\n");
	printf("1. 扫描\n");
	printf("2. 移动\n");
	printf("3. 交叉飞入\n");
	printf("4. 百叶窗特效\n");
	printf("5. 马赛克\n");
	printf("6. 水平栅条\n");
	printf("7. 图像渐显\n");
	printf("8. 中间扩张\n");
	printf("9. 中间收缩\n");
}

const char command_str[9][20] = 
{
	"1. scan",
	"2. move",
	"3. fly",
	"4. shutter",
	"5. mosaic",
	"6. grid",
	"7. appear",
	"8. expand",
	"9. shrink"
};

void print_to_window(int x, int y)
{
	int height = 30;
	int off = height + 10;
	Color c = BLACK;
	int i;

	for(i = 1; i <= 9; i++) {
		DrawText(TextFormat("%s", command_str[i-1]), x, y, height, c);
		y += off;
	}
}

void init_effect(Image image)
{
	effect_data.ori = image;
	effect_data.order = 0;

	effect_data.chg = ImageCopy(image);
	effect_data.image_size = GetPixelDataSize(image.width, image.height,\
		image.format);
	effect_data.pix_len = get_pixel_bytes(image.width, image.height,\
		effect_data.image_size);
	memset(effect_data.chg.data, 255, effect_data.image_size);
	effect_data.texture = LoadTextureFromImage(effect_data.chg);
}

void reset_effect_data(void)
{
	int size = effect_data.image_size;
	memset(effect_data.chg.data, 255, size);
}

void reset_effect_private(void)
{
	effect_data.order = 1;
	effect_data.start_pos = 0;
}

void reset_effect_all(void)
{
	reset_effect_private();
	reset_effect_data();
}

void destroy_effect(void)
{
	UnloadImage(effect_data.chg);
	UnloadTexture(effect_data.texture);
}

/*
* way:
* 1: 自上而下
* 2: 自下而上
* 3：自左向右
* 4：自右向左
*/
void effect_scan(my_image* data, int way)
{
	int line_size;
	int i, index;
	char *src, *dst;
	int len = 0;

	line_size = data->ori.width * data->pix_len;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if (way == 1) {//1: 自上而下
		//reset_effect_data();
		if (effect_data.start_pos == 0) {
			//first call
			reset_effect_data();
			hint_str = "scan: down to up";
		}
		len = effect_data.start_pos * line_size;
		memcpy(dst + len, src + len, line_size);
		effect_data.start_pos++;
		if (effect_data.start_pos >= effect_data.ori.height) {
			effect_data.start_pos = effect_data.ori.height;
			effect_data.order++;
			gstate = STATE_WAIT_NEXT;
		}
		UpdateTexture(data->texture, data->chg.data);
	} else if(way == 2) {//2: 自下而上
		if(effect_data.start_pos == effect_data.ori.height) {
			//first call
			effect_data.start_pos--;
			reset_effect_data();
			hint_str = "scan: down to up";
		}
		len = effect_data.start_pos * line_size;
		memcpy(dst + len, src + len, line_size);
		effect_data.start_pos--;
		if(effect_data.start_pos <= 0) {
			effect_data.start_pos = 0;
			effect_data.order++;
			gstate = STATE_WAIT_NEXT;
		}
		UpdateTexture(data->texture, data->chg.data);
	} else if(way == 3) { //3：自左向右
		if(effect_data.start_pos == 0) {
			//first
			reset_effect_data();
			hint_str = "scan: left to right";
		}
		for(i = 0; i < effect_data.ori.height; i++) {
			index = data->pix_len * (effect_data.start_pos + i * effect_data.ori.width);
			dst[index] = src[index]; 
			dst[index+1] = src[index+1];
			dst[index+2] = src[index+2];
		}
		effect_data.start_pos++;
		if (effect_data.start_pos >= effect_data.ori.width) {
			effect_data.start_pos = effect_data.ori.width;
			effect_data.order++;
			gstate = STATE_WAIT_NEXT;
		}
		UpdateTexture(data->texture, data->chg.data);

	}else { //4：自右向左
		if(effect_data.start_pos == effect_data.ori.width) {
			//first
			reset_effect_data();
			effect_data.start_pos--;
			hint_str = "scan: right to left";
		}
		for(i = 0; i < effect_data.ori.height; i++) {
			index = data->pix_len * (effect_data.start_pos + i * effect_data.ori.width);
			dst[index] = src[index];
			dst[index+1] = src[index+1];
			dst[index+2] = src[index+2];
		}
		effect_data.start_pos--;
		if (effect_data.start_pos <= 0) {
			effect_data.start_pos = 0;
			effect_data.order = 0;
			gstate = STATE_FINISH;
		}
		UpdateTexture(data->texture, data->chg.data);
	}
}

//水平向右移动或垂直向上移动
void effect_move(my_image* data, int way)
{
	int i, j, k, index_dst, index_src;
	char *dst, *src;
	int line_size = 0;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if(way == 1) {
		if (effect_data.start_pos == 0) {
			//first
			printf("水平向右移动\n");
			hint_str = "move to right";
			effect_data.start_pos = effect_data.ori.width - 1;
		}
		j = effect_data.start_pos;
		for (k = 0; j < effect_data.ori.width; j++, k++) {
			for(i = 0; i < effect_data.ori.height; i++) {
				index_src = data->pix_len *(j + i * effect_data.ori.width);
				index_dst = data->pix_len *(k + i * effect_data.ori.width);
				dst[index_dst] = src[index_src];
				dst[index_dst+1] = src[index_src+1];
				dst[index_dst+2] = src[index_src+2];
			}
		}
		effect_data.start_pos--;
		if (effect_data.start_pos <= 0) {
			effect_data.order++;
			gstate = STATE_WAIT_NEXT;
			effect_data.start_pos = 0;
		}
		UpdateTexture(data->texture, data->chg.data);

	} else {
		if (effect_data.start_pos == 0) {
			//first
			printf("垂直向上移动\n");
			hint_str = "move to up";
			effect_data.start_pos = 1;
		}
		reset_effect_data(); //clear previous data
		line_size = data->ori.width * data->pix_len;
		k = effect_data.ori.height - effect_data.start_pos;
		for (i = 0; i < effect_data.start_pos; i++, k++) {
			dst = (char*)effect_data.chg.data + line_size * k;
			src = (char*)effect_data.ori.data + line_size * i;
			memcpy(dst, src, line_size);
		}

		effect_data.start_pos++;
		if (effect_data.start_pos >= effect_data.ori.height) {
			effect_data.start_pos = 0;
			effect_data.order = 0;
			gstate = STATE_FINISH;
		}
		UpdateTexture(data->texture, data->chg.data);
	}

}

void effect_cross_fly(my_image* data)
{
	int left_start, right_start;
	int end;
	int i, j, k, index_dst, index_src;
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if (effect_data.start_pos == 0) {
		//first
		printf("交叉飞入\n");
		hint_str = "cross fly";
		effect_data.start_pos = 1;
	}

	left_start = effect_data.ori.width - effect_data.start_pos;
	right_start = effect_data.start_pos;
	end = effect_data.ori.height / 2;

	reset_effect_data(); //clear previous data

	//up: left to right
	j = left_start;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;
	for (k = 0; j < effect_data.ori.width; j++, k++) {
		for(i = 0; i < end; i++) {
			index_src = data->pix_len *(j + i * effect_data.ori.width);
			index_dst = data->pix_len *(k + i * effect_data.ori.width);
			dst[index_dst] = src[index_src];
			dst[index_dst+1] = src[index_src+1];
			dst[index_dst+2] = src[index_src+2];
		}
	}

	//down: right to left
	j = 0;
	k = left_start;
	for (; k < effect_data.ori.width; j++, k++) {
		for(i = end; i < effect_data.ori.height; i++) {
			index_src = data->pix_len *(j + i * effect_data.ori.width);
			index_dst = data->pix_len *(k + i * effect_data.ori.width);
			dst[index_dst] = src[index_src];
			dst[index_dst+1] = src[index_src+1];
			dst[index_dst+2] = src[index_src+2];
		}
	}

	effect_data.start_pos++;
	if (effect_data.start_pos >= effect_data.ori.width) {
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
	}
	UpdateTexture(data->texture, data->chg.data);
}

void effect_shutter(my_image* data)
{
	int line_size, N = 10, steps = 1;
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;
	line_size = data->ori.width * data->pix_len;
	steps = effect_data.ori.height / N;

	if (effect_data.start_pos == 0) {
		//first
		printf("百叶窗\n");
		hint_str = "horizontal shutter";
		effect_data.start_pos = 1;
	}
	int i, offset;
	for(i = 0; i < N; i++) {
		offset = i * steps * line_size;
		memcpy(dst + offset, src + offset,\
			effect_data.start_pos * line_size);
	}

	effect_data.start_pos++;
	if(effect_data.start_pos > steps) {
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
	}
	UpdateTexture(data->texture, data->chg.data);
}

//image size: 360 * 200
//block size: 64
#define MAX_AREA_NUMBER 6000
char area_bitmap[MAX_AREA_NUMBER];

//index >= 0 && index < AREA_NUMBER
void fill_area(my_image* data, int index)
{
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	int row_item, col_item;
	int row, col;
	row_item = data->ori.width / 8;
	col_item = data->ori.height / 8;
	row = index / row_item;
	col = index - row * row_item;

	int line_size = data->ori.width * data->pix_len;
	int offset, bytes, i;
	bytes = line_size / row_item;
	offset = row * 8 * line_size + col * bytes;
	for(i = 0; i < 8; i++) {
		//8*8方块，每行24字节，一共8行
		memcpy(dst + offset, src + offset, 24);
		offset += line_size;
	}
}

void effect_mosaic(my_image* data)
{
	int n = data->ori.width * data->ori.height / 64; //N area
	if (effect_data.start_pos == 0) {
		//first
		printf("马赛克\n");
		hint_str = "mosaic";
		memset(area_bitmap, 0, sizeof(area_bitmap));
	}

	int index;
	int i;
	for (i = 0; i < 10; i++) {
		index = GetRandomValue(0, n - 1); //max = n - 1
		if (!area_bitmap[index]) {
			fill_area(data, index);
			area_bitmap[index] = 1;
			effect_data.start_pos++;
		}
	}

	if(effect_data.start_pos >= (n - 100)) {
		//fill all
		memcpy(effect_data.chg.data, effect_data.ori.data, data->image_size);
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
	}
	UpdateTexture(data->texture, data->chg.data);
}


void effect_expand(my_image* data)
{
	int end, line_size;
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if (effect_data.start_pos == 0) {
		//first
		printf("中间扩张\n");
		hint_str = "expand from center";
	}

	end = effect_data.ori.height / 2;
	line_size = data->ori.width * data->pix_len;
	int start_line = end - effect_data.start_pos;
	int end_line = end + effect_data.start_pos;
	int lines = end_line - start_line + 1;
	memcpy(dst + start_line * line_size,\
		   src + start_line * line_size,\
		   lines * line_size);

	effect_data.start_pos++;
	if(effect_data.start_pos >= end) {
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
		effect_data.start_pos = 1;
	}
	UpdateTexture(data->texture, data->chg.data);
}

void effect_shrink(my_image* data)
{
	int end, line_size;
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if (effect_data.start_pos == 0) {
		//first
		printf("中间收缩\n");
		hint_str = "shrink to center";
		effect_data.start_pos = 1;
	}

	end = effect_data.ori.height / 2;
	line_size = data->ori.width * data->pix_len;

	//top to mid
	int bytes = effect_data.start_pos * line_size;
	memcpy(dst, src, bytes);
	//bottom to mid
	int offset =  effect_data.ori.height - effect_data.start_pos;
	offset = offset * line_size;
	memcpy(dst + offset, src + offset, bytes);

	effect_data.start_pos++;
	if (effect_data.start_pos > end) {
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
	}
	UpdateTexture(data->texture, data->chg.data);
}

void effect_grid(my_image* data)
{
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if (effect_data.start_pos == 0) {
		//first
		printf("水平栅条\n");
		hint_str = "horizontal gird";
		effect_data.start_pos = 1;
	}

	int i, j, k;
	int index_dst, index_src;
	j = effect_data.ori.width - effect_data.start_pos;
	//left to right
	for (k = 0; j < effect_data.ori.width; j++, k++) {
		for(i = 0; i < effect_data.ori.height; i++) {
			if(i%2)
				continue;
			index_src = data->pix_len *(j + i * effect_data.ori.width);
			index_dst = data->pix_len *(k + i * effect_data.ori.width);
			dst[index_dst] = src[index_src];
			dst[index_dst+1] = src[index_src+1];
			dst[index_dst+2] = src[index_src+2];
		}
	}
	//right to left
	j = 0;
	k = effect_data.ori.width - effect_data.start_pos;
	for (; k < effect_data.ori.width; j++, k++) {
		for(i = 0; i < effect_data.ori.height; i++) {
			if(i%2 == 0)
				continue;
			index_src = data->pix_len *(j + i * effect_data.ori.width);
			index_dst = data->pix_len *(k + i * effect_data.ori.width);
			dst[index_dst] = src[index_src];
			dst[index_dst+1] = src[index_src+1];
			dst[index_dst+2] = src[index_src+2];
		}
	}

	effect_data.start_pos++;
	if (effect_data.start_pos > data->ori.width) {
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
	}
	UpdateTexture(data->texture, data->chg.data);
}

void effect_appear(my_image* data)
{
	char *dst, *src;
	dst = (char*)effect_data.chg.data;
	src = (char*)effect_data.ori.data;

	if (effect_data.start_pos == 0) {
		//first
		printf("图像渐显\n");
		hint_str = "horizontal gird";
		effect_data.start_pos = 1;
	}

	int i;
	for(i = 0; i < effect_data.image_size; i++) {
		dst[i] = (src[i] * effect_data.start_pos) >> 6;
	}
	effect_data.start_pos++;
	if(effect_data.start_pos > 64) {
		effect_data.start_pos = 0;
		gstate = STATE_FINISH;
	}
	UpdateTexture(data->texture, data->chg.data);
}

void special_effect(int cmd, int startx, int starty)
{
	//1 - 9 is valid
	switch (cmd) {
	case 1: //扫描
		effect_scan(&effect_data, effect_data.order);
		break;
	case 2: //移动
		effect_move(&effect_data, effect_data.order);
		break;
	case 3: //交叉飞入
		effect_cross_fly(&effect_data);
		break;
	case 4: //百叶窗
		effect_shutter(&effect_data);
		break;
	case 5: //马赛克
		effect_mosaic(&effect_data) ;
		break;
	case 6: //水平栅条
		effect_grid(&effect_data) ;
		break;
	case 7: //图像渐显
		effect_appear(&effect_data);
		break;
	case 8: //中间扩张
		effect_expand(&effect_data);
		break;
	case 9: //中间收缩
		effect_shrink(&effect_data);
		break;
	default:
		break;
	}
}

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(int argc, char** argv)

{
    // Initialization
    //--------------------------------------------------------------------------------------

	char* file_path = debug_path;
	if (argc < 2) {
		printf("usage: basic image_file \n");
	} else {
		file_path = argv[1];
	}
    InitWindow(screenWidth, screenHeight, "特效展示");

	// NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
	printf("open file %s\n", file_path);
	cat = LoadImage(file_path);             // Load image in CPU memory (RAM)
    if(!cat.data)
		return -1;
	if (cat.width > 360) {
		int width = 360;
		int height = cat.height * 360 / cat.width;
		ImageResize(&cat, width, height);
	}
	init_effect(cat);
	//ImageCrop(&cat, (Rectangle){ 100, 10, 280, 380 });      // Crop an image piece
    //ImageFlipHorizontal(&cat);                              // Flip cropped image horizontally
    //ImageResize(&cat, 150, 200);                            // Resize flipped-cropped image

	gtexture = LoadTextureFromImage(cat);      // Image converted to texture, uploaded to GPU memory (VRAM)
	print_to_terminal();

    SetTargetFPS(60);               // Set our game to run at 60 frames-per-second
    //--------------------------------------------------------------------------------------

	int startx = 10;
	int starty = 30;
	int right_startx = startx + 20 + gtexture.width;
	int right_image_startx = right_startx + 20;
	int right_image_starty = starty;
	int effect_command = 0; //1 - 9 is valid

	char key = 0;
	int change = 0;
	Vector2 mouse = GetMousePosition();

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update
		key = (char)GetKeyPressed();
		if (key >= '1' && key <= '9') {
			change = 1;
			effect_command = key - '0';
		} else if(key == ' ') {
			if(gstate == STATE_WAIT_NEXT)
				gstate = STATE_ONGOING;
		} else if (key == ESC_KEY) {
			break;
		}

		if (change) {
			//take effect
			change = 0;
			hint_str = (char*)command_str[effect_command - 1];
			gstate = STATE_ONGOING;
			reset_effect_all();
			special_effect(effect_command, right_image_startx, right_image_starty);
		} else if (gstate == STATE_ONGOING) {
			special_effect(effect_command, right_image_startx, right_image_starty);
		}
        //----------------------------------------------------------------------------------
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();
        ClearBackground(RAYWHITE);

		if (hint_str) {
			DrawText(hint_str, right_image_startx, 0, 20, BLACK);
		}
		DrawTexture(gtexture, startx, starty, WHITE);
		print_to_window(startx, gtexture.height + starty + 20);
		DrawLine(right_startx, starty, right_startx, GetScreenHeight(), GRAY);
		DrawTexture(effect_data.texture, right_image_startx, right_image_starty, WHITE);

		mouse = GetMousePosition();
		DrawText(TextFormat("mouse(%d, %d)", (int)mouse.x, (int)mouse.y),\
			GetScreenWidth() - 240, GetScreenHeight() - 50, 20, BLACK);

		if (gstate == STATE_WAIT_NEXT) {
			DrawText("press space key to continue", \
			GetScreenWidth() - 800, GetScreenHeight() - 50, 20, BLACK);
		}

		EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
	destroy_effect();
	UnloadImage(cat);
	UnloadTexture(gtexture);       // Texture unloading

    //--------------------------------------------------------------------------------------
    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}
